﻿namespace Frameworks
{
    using System;
    using System.Reflection;
    using UnityEngine;

    internal interface ISingleton
    {
        void OnSingletonInit();
    }

    public abstract class Singleton<T> : ISingleton where T : Singleton<T>
    {
        private static T _instance;
        private static readonly object _lock = new object();

        public static T Instance
        {
            get
            {
                lock (_lock)
                {
                    if (_instance == null)
                    {
                        // 私有构造
                        var ctors = typeof(T).GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic);

                        // 无参构造
                        var ctor = Array.Find(ctors, c => c.GetParameters().Length == 0);

                        if (ctor == null)
                        {
                            throw new Exception("Non-Public Constructor() not found! in " + typeof(T));
                        }

                        // 创建实例
                        _instance = ctor.Invoke(null) as T;
                        _instance.OnSingletonInit();
                    }
                }
                return _instance;
            }
        }

        public virtual void OnSingletonInit() { }

        public virtual void Dispose()
        {
            _instance = null;
        }
    }

    public class MonoSingleton<T> : MonoBehaviour, ISingleton where T : MonoSingleton<T>
    {
        private static T _instance = null;
        private static bool _isApplicationQuitting = false;
        private static object _lock = new object();
        private static Func<GameObject> _defaultInitGOGetter;

        private bool _initialized;

        public bool IsInstance { get { return this == Instance; } }
        public static bool Active { get { return !_isApplicationQuitting && _instance != null; } }
        protected bool IsApplicationQuitting { get { return _isApplicationQuitting; } }

        public static T Instance
        {
            get
            {
                if (!Application.isPlaying || _isApplicationQuitting) { return null; }

                if (_instance != null) { return _instance; }

                lock (_lock)
                {
                    var instances = FindObjectsOfType<T>();
                    if (instances.Length > 0)
                    {
                        _instance = instances[0];
                        if (instances.Length > 1) { Debug.LogWarning("Multiple " + typeof(T).Name + " not supported!"); }
                    }

                    if (_instance == null)
                    {
                        GameObject defaultInitGO = null;

                        if (_defaultInitGOGetter != null)
                        {
                            defaultInitGO = _defaultInitGOGetter();
                        }

                        if (defaultInitGO == null)
                        {
                            defaultInitGO = new GameObject("[" + typeof(T).Name + "]" + " - instance") { /*hideFlags = HideFlags.HideInHierarchy*/ };
                        }

                        _instance = defaultInitGO.AddComponent<T>();
                    }

                    if (!_instance._initialized)
                    {
                        _instance._initialized = true;
                        _instance.OnSingletonInit();
                    }
                }
                return _instance;
            }
        }

        /// <summary>
        /// Must set before the instance being initialized
        /// </summary>
        public static void SetDefaultInitGameObjectGetter(Func<GameObject> getter) { _defaultInitGOGetter = getter; }

        public virtual void OnSingletonInit() { }

        public virtual void Dispose() { }

        protected virtual void OnApplicationQuit()
        {
            _isApplicationQuitting = true;
        }

        protected virtual void OnDestroy()
        {
            if (!_isApplicationQuitting && _instance == this)
            {
                _instance = null;
            }
        }
    }
}